namespace SharpGraphT.Graph.Builder;

public class GraphTypeBuilder<TV, TE> where TE : class, new() {
    private bool _undirected;
    private bool _directed;
    private bool _weighted;
    private bool _allowingMultipleEdges;
    private bool _allowingSelfLoops;
    private Func<TV> _vertexSupplier;
    private Func<TE> _edgeSupplier;

    public GraphTypeBuilder(bool directed, bool undirected) {
        _undirected = undirected;
        _directed = directed;
        _weighted = false;
        _allowingMultipleEdges = false;
        _allowingSelfLoops = false;
    }

    public static GraphTypeBuilder<TV, TE> Directed() => new(true, false);

    public static GraphTypeBuilder<TV, TE> Undirected() => new(false, true);

    public static GraphTypeBuilder<TV, TE> Mixed() => new(true, true);

    public static GraphTypeBuilder<TV, TE> ForGraphType(IGraphType type) {
        var builder = new GraphTypeBuilder<TV, TE>(
            type.IsDirected || type.IsMixed, type.IsUndirected || type.IsMixed);
        builder._weighted = type.IsWeighted;
        builder._allowingSelfLoops = type.IsAllowingSelfLoops;
        builder._allowingMultipleEdges = type.IsAllowingMultipleEdges;
        return builder;
    }

    public static GraphTypeBuilder<TV, TE> ForGraphType(IGraph<TV, TE> graph) {
        var builder = ForGraphType(graph.Type);
        builder._vertexSupplier = graph.VertexSupplier;
        builder._edgeSupplier = graph.EdgeSupplier;
        return builder;
    }

    public GraphTypeBuilder<TV, TE> Weighted(bool weighted) {
        _weighted = weighted;
        return this;
    }

    public GraphTypeBuilder<TV, TE> AllowingSelfLoops(bool allowingSelfLoops) {
        _allowingSelfLoops = allowingSelfLoops;
        return this;
    }

    public GraphTypeBuilder<TV, TE> AllowingMultipleEdges(
        bool allowingMultipleEdges) {
        _allowingMultipleEdges = allowingMultipleEdges;
        return this;
    }

    public GraphTypeBuilder<TV, TE> VertexSupplier(Func<TV> vertexSupplier) {
        _vertexSupplier = vertexSupplier;
        return this;
    }

    public GraphTypeBuilder<TV, TE> EdgeSupplier(Func<TE> edgeSupplier) {
        _edgeSupplier = edgeSupplier;
        return this;
    }

    public IGraphType BuildType() {
        var typeBuilder = new DefaultGraphType.Builder();
        if (_directed && _undirected) {
            typeBuilder = typeBuilder.Mixed();
        } else if (_directed) {
            typeBuilder = typeBuilder.Directed();
        } else if (_undirected) {
            typeBuilder = typeBuilder.Undirected();
        }

        return typeBuilder.AllowMultipleEdges(_allowingMultipleEdges)
            .AllowSelfLoops(_allowingSelfLoops).Weighted(_weighted).Build();
    }

    public GraphBuilder<TV, TE, IGraph<TV, TE>> BuildGraphBuilder() =>
        new(BuildGraph());

    public IGraph<TV, TE> BuildGraph() {
        if (_directed && _undirected) {
            throw new InvalidOperationException(
                "Mixed graphs are not supported");
        }

        if (_directed) {
            if (_allowingSelfLoops && _allowingMultipleEdges) {
                return _weighted
                    ? new DirectedWeightedPseudograph<TV, TE>(_vertexSupplier,
                        _edgeSupplier)
                    : new DirectedPseudograph<TV, TE>(_vertexSupplier,
                        _edgeSupplier, false);
            }

            if (_allowingMultipleEdges) {
                return _weighted
                    ? new DirectedWeightedMultigraph<TV, TE>(_vertexSupplier,
                        _edgeSupplier)
                    : new DirectedMultigraph<TV, TE>(_vertexSupplier,
                        _edgeSupplier, false);
            }

            if (_allowingSelfLoops) {
                return _weighted
                    ? new DefaultDirectedWeightedGraph<TV, TE>(_vertexSupplier,
                        _edgeSupplier)
                    : new DefaultDirectedGraph<TV, TE>(_vertexSupplier,
                        _edgeSupplier, false);
            }

            if (_weighted) {
                return new SimpleDirectedWeightedGraph<TV, TE>(_vertexSupplier,
                    _edgeSupplier);
            }

            return new SimpleDirectedGraph<TV, TE>(_vertexSupplier,
                _edgeSupplier, false);
        }

        if (_allowingSelfLoops && _allowingMultipleEdges) {
            return _weighted
                ? new WeightedPseudograph<TV, TE>(_vertexSupplier,
                    _edgeSupplier)
                : new Pseudograph<TV, TE>(_vertexSupplier, _edgeSupplier,
                    false);
        }

        if (_allowingMultipleEdges) {
            return _weighted
                ? new WeightedMultigraph<TV, TE>(_vertexSupplier, _edgeSupplier)
                : new Multigraph<TV, TE>(_vertexSupplier, _edgeSupplier, false);
        }

        if (_allowingSelfLoops) {
            return _weighted
                ? new DefaultUndirectedWeightedGraph<TV, TE>(_vertexSupplier,
                    _edgeSupplier)
                : new DefaultUndirectedGraph<TV, TE>(_vertexSupplier,
                    _edgeSupplier, false);
        }

        return _weighted
            ? new SimpleWeightedGraph<TV, TE>(_vertexSupplier, _edgeSupplier)
            : new SimpleGraph<TV, TE>(_vertexSupplier, _edgeSupplier, false);
    }
}